package org.summer.gateway.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.summer.gateway.cache.UserCacheHandler;
import org.summer.gateway.pojo.SecurityRole;
import org.summer.gateway.pojo.SecurityUser;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 网关安全用户服务类,如果传入的用户名为null后者"",直接异常处理,处理逻辑:
 * 1. 先从缓存中查找用户
 *      1.1 查找用户后确认用户名是否存在
 *      1.2 如果找到的用户名为null则抛出用户未找到异常
 * 2. 如果缓存中不存在用户信息,则查数据库
 *      2.1 如果从数据库中查找到用户信息,缓存到redis中并返回用户信息
 *      2.2 如果从数据库中查不到用户信息,则缓存一个空用户信息,不是null
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class SecurityUserService implements ReactiveUserDetailsService {
    private final DatabaseClient databaseClient;
    private final UserCacheHandler cacheHandler;
    @Override
    public Mono<UserDetails> findByUsername(String username) {
        log.debug("用户登录请求第 2 步,根据用户名查找用户信息: {}", username);
        if (!StringUtils.hasText(username)){
            log.warn("用户名为空,无法登录");
            throw new UsernameNotFoundException("用户名为空,无法找到登录用户");
        }
        return cacheHandler
                .get(username)
                .doOnNext(e -> {
                    if (e.getUsername() == null){
                        log.debug("从缓存读取到空用户信息,抛出用户未找到异常,避免频繁查询数据库");
                        throw new UsernameNotFoundException("用户[ %s ]不存在".formatted(username));
                    }
                })
                .switchIfEmpty(this.loadUserByUsername(username))
                .cast(UserDetails.class);
    }

    /**
     * 根据用户名查找用户信息和用户拥有的角色信息
     * @param username 用户名
     * @return 用户信息,包含用户角色列表
     */
    private Mono<SecurityUser> loadUserByUsername(String username) {
        return databaseClient.sql("""
                        SELECT u.id as userId,u.username,u.password,u.identity,u.avatar,u.enabled,u.account_non_locked,u.account_non_expired,u.credentials_non_expired,
                               r.id as roleId,r.authority,r.`explain`
                        FROM security_user u
                        LEFT JOIN security_user_role ur
                            ON u.id = ur.user_id AND u.`delete` = FALSE AND ur.`delete` = FALSE
                        LEFT JOIN security_role r
                            ON ur.role_id = r.id AND r.`delete` = FALSE
                        WHERE u.username = :username
                        """)
                .bind("username", username)
                .fetch()
                .all()
                .bufferUntilChanged(keySet -> keySet.get("username"))
                .map(resultSet -> {
                    Map<String, Object> map = resultSet.getFirst();
                    SecurityUser user = new SecurityUser();
                    String userId = map.get("userId").toString();
                    String username_ = map.get("username").toString();
                    String password = map.get("password").toString();
                    String identity = map.get("identity").toString();
                    String avatar = map.get("avatar").toString();
                    String enabled = map.get("enabled").toString();
                    String accountNonLocked = map.get("account_non_locked").toString();
                    String accountNonExpired = map.get("account_non_expired").toString();
                    String credentialsNonExpired = map.get("credentials_non_expired").toString();
                    user.setId(Long.valueOf(userId));
                    user.setUsername(username_);
                    user.setPassword(password);
                    user.setIdentity(identity);
                    user.setAvatar(avatar);
                    user.setEnabled(Integer.parseInt(enabled) == 1);
                    user.setAccountNonLocked(Integer.parseInt(accountNonLocked) == 1);
                    user.setAccountNonExpired(Integer.parseInt(accountNonExpired) == 1);
                    user.setCredentialsNonExpired(Integer.parseInt(credentialsNonExpired) == 1);
                    List<SecurityRole> authorities = new ArrayList<>() ;
                    resultSet.forEach(row -> {
                        Object roleId = row.get("roleId");
                        Object authority = row.get("authority");
                        Object explain = row.get("explain");
                        if (Objects.nonNull(roleId) && Objects.nonNull(authority)) {
                            SecurityRole role = new SecurityRole();
                            role.setId(Long.valueOf(roleId.toString()));
                            role.setAuthority(authority.toString());
                            role.setExplain(explain);
                            authorities.add(role);
                        }
                    });
                    user.setAuthorities(authorities);
                    return user;
                })
                .next()
                .switchIfEmpty(Mono.defer(() -> {
                    log.info("用户没有找到,缓存默认用户到redis,避免频繁读取数据库,缓存成功后抛出用户不存在异常,缓存持续时间: 120 秒");
                    return cacheHandler.set(username,new SecurityUser(),120L)
                            .flatMap(success -> Mono.error(new UsernameNotFoundException("用户[ %s ]不存在".formatted(username))));
                }))
                .flatMap(user -> cacheHandler.set(username,user).thenReturn(user));
    }
}
